#include <stdio.h>
#include <string.h>

#include "eeprom.h"
#include "timer.h"
#include "tick.h"

#undef TRACE_LEVEL
#undef TRACE_TITLE
#undef TRACE_TITLE
#define TRACE_LEVEL	TRACE_LEVEL_DEBUG
#define TRACE_NAME  TRACE_NAME_DISABLE
#define TRACE_TITLE "ROM"
#include "trace.h"


#define GPIOR_CFGIO_OUTPUT(GPIOx, Pin)	GPIOx->CRL &= ~(0xFu << (Pin << 2));GPIOx->CRL |= (0x3u << (Pin << 2))
#define GPIOR_CFGIO_INPUT(GPIOx, Pin)   GPIOx->CRL &= ~(0xFu << (Pin << 2));GPIOx->CRL |= (0x8u << (Pin << 2))
#define GPIOR_SETIO(GPIOx, Pin)			GPIOx->BSRR = (1u << (Pin))
#define GPIOR_CLRIO(GPIOx, Pin) 		GPIOx->BRR = (1u << (Pin))
#define GPIOR_GETIO(GPIOx, Pin)			((GPIOx->IDR & (1u << (Pin))) ? 1 : 0)
#define GPIOR_DELAYUS(US)				delay_us(US)

#define GPIOR_E2PROM_WP		1

#define I2C_PORT 			GPIOB
#define I2C_WP_PIN			5
#define I2C_SCL_PIN			6
#define I2C_SDA_PIN			7

#define E2PROM_WP_PORT		I2C_PORT
#define E2PROM_WP_PIN		I2C_WP_PIN


void I2C_config(i2c_mode_t t);


/* I2C timeing parameters in ns */
typedef struct _I2C_TIMING {
	uint8 	start_hold;
	uint8 	clk_low;
	uint8 	clk_high;
	uint8 	dat_hold;
	uint8 	dat_setup;
	uint8 	stop_hold;
	uint8 	bus_free;
	uint8 	clk_low_sub_dat_hold;
} I2C_TIMING_T;


/* STD speed config parameters */
/*const static I2C_TIMING_T I2C_STD_SPDPA = {4000, 4700, 4000, 5, 250, 4000, 4700, 4600}*/
static I2C_TIMING_T I2C_STD_SPDPA  = {40, 47, 40, 1, 3, 40, 47, 47};

/* HIGH speed config parameters */
/* const static I2C_TIMING_T I2C_HIGH_SPDPA = { 600, 1300,  600, 0, 100,  600, 1300, 1300}; */
static I2C_TIMING_T I2C_HIGH_SPDPA = {6, 13,  6, 1, 1,  6, 13, 13};

/* host only mode */
static I2C_TIMING_T  *I2C_TP = &I2C_STD_SPDPA;

/*
 * @ init I2C GPIO
 */
static __inline void I2C_GPIO_INIT() {
	//I2C_PLAT_INIT();
	GPIOR_CFGIO_OUTPUT(I2C_PORT, I2C_SCL_PIN);
	GPIOR_CFGIO_OUTPUT(I2C_PORT, I2C_SDA_PIN);
} 

/*
 * @ open I2C port
 */
int I2C_open(i2c_mode_t type) {
	I2C_GPIO_INIT();
	I2C_config(type);
	return 0;
}

//*----------------------------------------------------------------------------
//*  Name     : i2c_config()
//*  Brief    : Config I2C bus speed
//*  Argument : I2CSPD_STD or I2CSPD_HIGH
//*  Return   : None
//*----------------------------------------------------------------------------
void I2C_config(i2c_mode_t t) {
	switch(t) {
	case I2C_STANDARD_MODE:
		I2C_TP = &I2C_STD_SPDPA;
		break;
	case I2C_HIGHSPEED_MODE:
		I2C_TP = &I2C_HIGH_SPDPA;
		break;
	default:
		I2C_TP = &I2C_STD_SPDPA;	
	}
} 

//*----------------------------------------------------------------------------
//*  Name     : i2c_start()
//*  Brief    : Write a start conditon on I2C bus
//*  Argument : None
//*  Return   : None
//*----------------------------------------------------------------------------
void I2C_start() {
	// Start condition: CLK = H, DAT = H -> L
	GPIOR_CFGIO_OUTPUT(I2C_PORT, I2C_SCL_PIN);
	GPIOR_CFGIO_OUTPUT(I2C_PORT, I2C_SDA_PIN);
	
	/*  SDA  L/H -> H -> L -> L
 	 *	SCL	   L -> H -> H -> L
 	 * */
	GPIOR_SETIO(I2C_PORT, I2C_SDA_PIN);
	GPIOR_DELAYUS(I2C_TP->dat_hold);
	GPIOR_SETIO(I2C_PORT, I2C_SCL_PIN);
	GPIOR_SETIO(I2C_PORT, I2C_SCL_PIN);
	GPIOR_SETIO(I2C_PORT, I2C_SCL_PIN);
	GPIOR_CLRIO(I2C_PORT, I2C_SDA_PIN);
	GPIOR_DELAYUS(I2C_TP->start_hold);
	GPIOR_CLRIO(I2C_PORT, I2C_SCL_PIN);
}

//*----------------------------------------------------------------------------
//*  Name     : i2c_stop()
//*  Brief    : Write a stop condition on I2C bus
//*  Argument : None 
//*  Return   : None
//*----------------------------------------------------------------------------
void I2C_stop() {
	// Stop condition: CLK = H, DAT = L -> H
	GPIOR_CFGIO_OUTPUT(I2C_PORT, I2C_SDA_PIN);
	GPIOR_CFGIO_OUTPUT(I2C_PORT, I2C_SCL_PIN);	
	
	
	/*	SDA	 L/H -> L -> L -> H
 	 *	SCL	   L -> L -> H -> H	
 	 * */
	GPIOR_CLRIO(I2C_PORT, I2C_SDA_PIN);
	GPIOR_DELAYUS(I2C_TP->clk_low_sub_dat_hold);
	GPIOR_SETIO(I2C_PORT, I2C_SCL_PIN);
	GPIOR_DELAYUS(I2C_TP->stop_hold);
	GPIOR_SETIO(I2C_PORT, I2C_SDA_PIN);
	GPIOR_DELAYUS(I2C_TP->bus_free);
}

//*----------------------------------------------------------------------------
//*  Name     : i2c_write_bit()
//*  Brief    : Write a bit on I2C bus
//*  Argument : bit = 0 or bit > 0
//*  Return   : None
//*----------------------------------------------------------------------------
static void i2c_write_bit(boolean bit) {					   		  	
	GPIOR_DELAYUS(I2C_TP->dat_hold);
	if(bit) {
		GPIOR_SETIO(I2C_PORT, I2C_SDA_PIN);
	} else {
		GPIOR_CLRIO(I2C_PORT, I2C_SDA_PIN);
	}
	// remain clock low
	GPIOR_DELAYUS(I2C_TP->clk_low_sub_dat_hold);
	// clock high
	GPIOR_SETIO(I2C_PORT, I2C_SCL_PIN);
	GPIOR_DELAYUS(I2C_TP->clk_high);
	GPIOR_CLRIO(I2C_PORT, I2C_SCL_PIN);	
}



//*----------------------------------------------------------------------------
//*  Name     : i2c_read_bit()
//*  Brief    : read a bit from I2C bus
//*  Argument : None
//*  Return   : bit value: 0 or NOT
//*----------------------------------------------------------------------------
static int i2c_read_bit() {
	int state;
	
	GPIOR_DELAYUS(I2C_TP->clk_low);
	GPIOR_SETIO(I2C_PORT, I2C_SCL_PIN);
	GPIOR_DELAYUS(I2C_TP->clk_high);
	state = GPIOR_GETIO(I2C_PORT, I2C_SDA_PIN);
	GPIOR_CLRIO(I2C_PORT, I2C_SCL_PIN);
	return state;
}



//*----------------------------------------------------------------------------
//*  Name     : i2c_write_byte()
//*  Brief    : Write a byte on I2C bus
//*  Argument : Data value which is 8bits
//*  Return   : ACK = 0, NACK = -1
//*----------------------------------------------------------------------------
int I2C_write_byte(uint8 c) {
	int i;
	
	GPIOR_CFGIO_OUTPUT(I2C_PORT, I2C_SDA_PIN);
	for( i = 0; i < 8; i++) {
		i2c_write_bit((c << i) & 0x80);
	}
	GPIOR_CFGIO_INPUT(I2C_PORT, I2C_SDA_PIN);
	// check ack
	if(i2c_read_bit()) {
		// no ack
		return -1;
	} else {
		// ack
		return 0;
	}
}



//*----------------------------------------------------------------------------
//*  Name     : i2c_read_byte() 
//*  Brief    : Read a byte from I2C bus
//*  Argument : FALSE = No ACK, TRUE = ACK  
//*  Return   : Data read from I2C bus
//*----------------------------------------------------------------------------
int I2C_read_byte(int ACK) {
	uint8 buf = 0;
	int i;
	
	GPIOR_CFGIO_INPUT(I2C_PORT, I2C_SDA_PIN);
	for( i = 0; i < 8; i ++) {
		buf <<= 1;
		if(i2c_read_bit()) {
			buf |= 1;
		}
	}
	// ack or not
	GPIOR_CFGIO_OUTPUT(I2C_PORT, I2C_SDA_PIN);
	if(ACK) {
		i2c_write_bit(0);
	} else {
		i2c_write_bit(1);
	}
	return buf;	
}

/*
 * @ i2c close
 */
int I2C_close() {
 	GPIOR_CFGIO_INPUT(I2C_PORT, I2C_SDA_PIN);
	GPIOR_CFGIO_INPUT(I2C_PORT, I2C_SCL_PIN);
	//I2C_PLAT_DEINIT();
	return 0;
}


/* device sub address length in byte */
static uint8 I2C_SUBADDR_LENB;
/* device address for reading */	
static uint8 I2C_RADDR;
/* device address for writing */		
static uint8 I2C_WADDR;


static void EEPROM_WPIO_INIT() {
	GPIOR_CFGIO_OUTPUT(E2PROM_WP_PORT, E2PROM_WP_PIN);
}


//*----------------------------------------------------------------------------
//*  Name     : write_sub_addr()
//*  Brief    : Write sub address
//*  Argument : I2C host pointer
//*  Return   : -1 / 0
//*----------------------------------------------------------------------------
static int write_sub_addr(uint32 sub_addr) {
	int i;
	
	for(i = 0; i < I2C_SUBADDR_LENB; i++) {
		if(I2C_write_byte((sub_addr >> ((I2C_SUBADDR_LENB - 1 - i) * 8) & 0xff)) != 0) {
			return -1;
		}	
	}
	return 0;
}

/* 
** @ E2PROM open with sub-address length and device address
**/
int E2PROM_open(uint8 sub_addr_lenb, uint8 dev_addr) {
	if(I2C_open(I2C_STANDARD_MODE) != 0)
		return -1;
#if (GPIOR_E2PROM_WP)
	EEPROM_WPIO_INIT();
#endif // if (GPIOR_EEPROM_WP)
	I2C_SUBADDR_LENB = sub_addr_lenb;
	I2C_RADDR = dev_addr | 0x01;
	I2C_WADDR = dev_addr & 0xfe;
	return 0;
}


/*
** @ E2PROM read with sub-address
**/
int E2PROM_read(uint32 sub_addr, uint8 * buf, uint32 len) {
	int i;
	
	// start condition
	I2C_start();
	// command: write
	if(I2C_write_byte(I2C_WADDR) != 0) {
		I2C_stop();
		return -1;
	}
	// sub sddress
	if(write_sub_addr(sub_addr) != 0) {
		I2C_stop();
		return -1;
	}
	// retart condition
	I2C_start();
	// commond: read
	if(I2C_write_byte(I2C_RADDR) != 0) {
		I2C_stop();
		return -1;
	}	
	// read datas
	for(i = 0; i < (len - 1); i ++) {
		*(buf++) = I2C_read_byte(TRUE);	
	}
	// last byte without ack
	*buf = I2C_read_byte(FALSE);
	I2C_stop();
	return 0;	
}


/*
** @ E2PROM write with sub-address
**/
int E2PROM_write(uint32 sub_addr, const uint8 * buf, uint32 len) {
	int i;
	
	// start condition
	I2C_start();
	// command: write
	if(I2C_write_byte(I2C_WADDR) != 0) {
		I2C_stop();
		return -1;
	}
	// sub sddress
	if(write_sub_addr(sub_addr) != 0) {
		I2C_stop();
		return -1;
	}
	// start write
	for(i = 0; i < len; i ++) {
		if(I2C_write_byte(*(buf++)) != 0) {
			I2C_stop();
			return -1;
		}	
	}
	I2C_stop();
	
	delay_ms(20);
	return 0;			  
}

/*
** @ enable the write protect
**/
void E2PROM_wp_enable() {
#if (GPIOR_E2PROM_WP)
	GPIOR_SETIO(E2PROM_WP_PORT, E2PROM_WP_PIN);	
#endif
}

/*
** @ disable the write protect
**/
void E2PROM_wp_disable() {
#if (GPIOR_E2PROM_WP)	
	GPIOR_CLRIO(E2PROM_WP_PORT, E2PROM_WP_PIN);
#endif
}


/*
** @ close E2PROM
**/
int E2PROM_close(void) {
	I2C_close();
#if (GPIOR_E2PROM_WP)
	//E2PROMWP_PLAT_DEINIT();
#endif // if (GPIOR_EEPROM_WP)
	return 0;
}


#include "tick.h"
int E2PROM_Main(void) {
	unsigned char buffer[32] = {0};
	int i;
	int err;
	
	if(E2PROM_open(1, 0xA0) != 0) {
		TRACE_DEBUG("open E2PROM error!");
		return -1;
	}
	
	/* write E2PROM */
	//TRACE_DEBUG("write to E2PROM [%s]", buffer);
	for(i = 0; i < sizeof(buffer); i ++) {
		buffer[i] = i;
	}
	
	E2PROM_wp_disable();
	err = E2PROM_write(0, buffer, 16); 
 	E2PROM_wp_enable();
	
	if(err != 0) {
		TRACE_ERROR("write E2PROM stage1 error");
		return -1;
	}
	
	delay_ms(20);
	
	E2PROM_wp_disable();
	err = E2PROM_write(16, buffer + 16, 16); 
 	E2PROM_wp_enable();
	
	if(err != 0) {
		TRACE_ERROR("write E2PROM stage2 error");
		return -1;
	}
 
	/* delay 20ms */
	delay_ms(20);
	
	memset(buffer, 0, sizeof(buffer));
	
	/* read back to check */
	if(E2PROM_read(0, buffer, sizeof(buffer)) != 0) {
	 	TRACE_ERROR("read E2PROM error");
		return -1;
	}
	
	for(i = 0; i < sizeof(buffer); i ++) {
		TRACE_DEBUG_WP("%d,", buffer[i]);
	}
	//TRACE_DEBUG("read from E2PROM [%s]", buffer);
	return 0;
}	


